package cn.backflow.web.treeable;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 树形对象通用接口, 工具类
 *
 * @author backflow
 * 2014年10月16日 上午10:02:51
 */
public interface Treeable {

    Comparable getId();

    String getName();

    default Comparable getParent() {
        return null;
    }

    default String getIcon() {
        return "";
    }

    List<Treeable> getChildren();

    void addChild(Treeable treeable);

    Treenode asTreenode();

    static List<Treeable> tree(Collection<? extends Treeable> list) {
        if (list == null || list.isEmpty()) return Collections.emptyList();
        Map<Comparable, Treeable> map =
                list.stream().collect(Collectors.toMap(Treeable::getId, Function.identity()));
        return tree(map);
    }

    static List<Treeable> tree(Map<? extends Comparable, ? extends Treeable> map) {
        return tree(map, 0);
    }

    /**
     * 将数据封装成树形结构，无限级深，只需遍历一次
     */
    static List<Treeable> tree(Map<? extends Comparable, ? extends Treeable> map, Integer root) {
        if (map == null || map.isEmpty())
            return Collections.emptyList();
        List<Treeable> tree = new ArrayList<>(map.size());
        for (Treeable v : map.values()) {
            Comparable pid = v.getParent();
            Treeable parent = map.get(pid);
            if (Objects.equals(pid, root))
                tree.add(v);
            else if (parent != null) {
                parent.addChild(v);
            }
        }
        return tree;
    }

    /**
     * 将数据封装成 jstree.js 需要的 树形结构
     */
    static List<Treenode> jstree(Collection<? extends Treeable> nodes, Collection<Comparable> selected, Collection<Comparable> unselectable) {

        if (nodes.isEmpty())
            return Collections.emptyList();

        int size = nodes.size();
        Map<Integer, Treenode> map = new HashMap<>(size);
        for (Treeable n : nodes) {
            Treenode node = n.asTreenode();
            if (selected != null && selected.contains(node.id))
                node.checked = true;
            else if (unselectable != null && unselectable.contains(node.id))
                node.disabled = true;
            map.put(node.id, node);
        }
        List<Treenode> tree = new ArrayList<>(size);
        for (Treenode n : map.values()) {
            Comparable pid = n.pid;
            Treenode parent = map.get(pid);
            if (pid.equals(0) || parent == null) { // 根节点
                tree.add(n);
            } else {
                parent.addChild(n);
            }
        }
        return tree;
    }

    /**
     * 构造 jquery.treetable.js 需要的数据结构 (子元素紧跟在父元素之后)
     *
     * @param treeables 包含所有节点的列表
     * @param pid       父元素ID
     * @param tree      排序好的权限列表
     */
    static List<Treeable> sort(Collection<? extends Treeable> treeables, Comparable pid, List<Treeable> tree) {
        for (Treeable t : treeables) {
            // 当前元素不是pid的子元素, 或已被添加过时, 跳过
            if (!t.getParent().equals(pid) || tree.contains(t)) continue;
            // 追加到列表当中
            tree.add(t);
            // 追加子元素
            sort(treeables, t.getId(), tree);
        }
        return tree;
    }

    /**
     * treeview helper class
     * Created by Nandy on 2016/5/14.
     * Expected format of the node (there are no required fields)
     * <pre>
     * {
     *   id        : string     // will be autogenerated if omitted
     *   name      : string     // node name
     *   icon      : string     // string for icon class
     *   opened    : boolean    // is node open
     *   checked   : boolean    // is node selected
     *   disabled  : boolean    // is node disabled
     *   children  : array      // array or objects
     * }
     * </pre>
     */
    @SuppressWarnings("WeakerAccess")
    class Treenode {
        public Integer id;
        public Integer pid;
        public String name;
        public String icon;
        public boolean opened;
        public boolean checked;
        public boolean disabled;
        public List<Treenode> children;

        public void addChild(Treenode n) {
            if (children == null) {
                children = new ArrayList<>();
            }
            children.add(n);
        }
    }
}